package com.btm.dorisinfo.service;

import com.btm.dorisinfo.Util.TableInfoIndexBuilder;
import com.btm.dorisinfo.config.DynamicalConstant;
import com.btm.dorisinfo.model.TableInfo;
import com.btm.common.model.PageResult;
import com.meilisearch.sdk.Client;
import com.meilisearch.sdk.Index;
import com.meilisearch.sdk.SearchRequest;
import com.meilisearch.sdk.model.Searchable;
import com.meilisearch.sdk.model.TaskInfo;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import com.btm.dorisinfo.mapper.TableStructureMapper;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * 表结构服务类
 * 提供数据库表结构和Meilisearch索引结构的查询和更新功能
 * 
 * @author btm
 * @since 2024-03-21
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class TableStructureService {

    /**
     * Spring JDBC模板
     * 用于执行数据库操作
     */
    private final JdbcTemplate jdbcTemplate;

    /**
     * Meilisearch客户端
     * 用于操作Meilisearch搜索引擎
     */
    private final Client meilisearchClient;

    /**
     * 动态常量
     * 用于获取Meilisearch索引名称
     */
    private final DynamicalConstant dynamicalConstant;

    // 从配置文件中获得指定的数据库schema命名
    @Value("${database.schema-name}")
    private String targetSchemaName;

    /**
     * 获取数据库表结构
     * 从information_schema.COLUMNS表中查询指定表的完整结构信息
     *
     * @param tableSchema 数据库名
     * @param tableName 要查询的表名
     * @return 表结构信息，如果表不存在则返回null
     */
    public TableInfo getDatabaseTableStructure(String tableSchema, String tableName) {
        // 若有指定的schema，则使用指定的schema
        if (StringUtils.hasText(targetSchemaName)) {
            tableSchema = targetSchemaName;
        }
        // 首先查询表的基本信息
        List<Map<String, Object>> tableInfo = jdbcTemplate.queryForList(
            TableStructureMapper.GET_TABLE_STRUCTURE,
            tableSchema, 
            tableName
        );
        
        if (tableInfo.isEmpty()) {
            return new TableInfo();
        }

        // 获取表的基本信息
        Map<String, Object> tableBaseInfo = tableInfo.get(0);

        // 查询表的字段信息
        List<Map<String, Object>> columns = jdbcTemplate.queryForList(
            TableStructureMapper.GET_TABLE_COLUMNS_STRUCTURE,
            tableSchema, 
            tableName
        );
        
        if (columns.isEmpty()) {
            return new TableInfo();
        }

        // 转换所有列信息，并按照列名进行字典序排序
        List<TableInfo.ColumnInfo> columnInfos = columns.stream()
                .map(this::mapToColumnInfo)
                .sorted((c1, c2) -> c1.getColumnName().compareTo(c2.getColumnName()))
                .collect(Collectors.toList());

        // 构建并返回表结构信息
        return TableInfo.builder()
                .tableSchema((String) tableBaseInfo.get("TABLE_SCHEMA"))
                .tableName((String) tableBaseInfo.get("TABLE_NAME"))
                .tableComment((String) tableBaseInfo.get("TABLE_COMMENT"))
                .columns(columnInfos)
                .build();
    }

    /**
     * 获取所有匹配的Meilisearch索引结构（分页）
     * @param tableName 索引名称关键字
     * @param page 页码
     * @param size 每页数量
     * @return 分页结果
     */
    public PageResult<TableInfo> getMeilisearchTableStructures(String tableName, int page, int size) {
        try {
            Index index = meilisearchClient.index(dynamicalConstant.indexName);
            SearchRequest searchRequest = new SearchRequest(tableName)
                    .setOffset((page-1)*size)
                    .setLimit(size);
            Searchable searchResult = index.search(searchRequest);
            ArrayList<HashMap<String, Object>> hits = searchResult.getHits();
            // Meilisearch不直接返回hasNext，这里用数据量判断
            boolean hasNext = hits.size() == size; // 仅作为简单判断
            List<TableInfo> results = new ArrayList<>();
            for (HashMap<String, Object> document : hits) {
                List<TableInfo.ColumnInfo> columns = new ArrayList<>();
                List<Map<String, Object>> columnMaps = (List<Map<String, Object>>) document.get("columns");
                if (columnMaps != null) {
                    for (Map<String, Object> columnMap : columnMaps) {
                        TableInfo.ColumnInfo columnInfo = TableInfo.ColumnInfo.builder()
                                .columnName((String) columnMap.get("columnName"))
                                .dataType((String) columnMap.get("dataType"))
                                .columnComment((String) columnMap.get("columnComment"))
                                .columnKey((String) columnMap.get("columnKey"))
                                .isNullable((String) columnMap.get("isNullable"))
                                .columnDefault((String) columnMap.get("columnDefault"))
                                .build();
                        columns.add(columnInfo);
                    }
                }
                columns.sort((c1, c2) -> c1.getColumnName().compareTo(c2.getColumnName()));
                TableInfo tableInfo = TableInfo.builder()
                        .id(((Double)document.get("id")).longValue())
                        .tableSchema((String) document.get("tableSchema"))
                        .tableName((String) document.get("tableName"))
                        .tableComment((String) document.get("tableComment"))
                        .columns(columns)
                        .build();
                results.add(tableInfo);
            }
            return new PageResult<>(results, hasNext);
        } catch (Exception e) {
            log.error("从Meilisearch获取表结构列表失败: {}", e.getMessage(), e);
            return new PageResult<>(new ArrayList<>(), false);
        }
    }

    /**
     * 更新数据库表字段注释
     * 通过ALTER TABLE语句更新表中各字段的注释信息
     *
     * @param tableStructure 包含更新后注释信息的表结构
     */
    public void updateDatabaseComment(TableInfo tableStructure) {
        for (TableInfo.ColumnInfo column : tableStructure.getColumns()) {
            String sql = "ALTER TABLE " + tableStructure.getTableName() + 
                        " MODIFY COLUMN " + column.getColumnName() + 
                        " " + column.getDataType() + 
                        " COMMENT '" + column.getColumnComment() + "'";
            jdbcTemplate.execute(sql);
        }
    }

    /**
     * 更新Meilisearch索引结构
     * 更新Meilisearch中指定索引的结构信息
     *
     * @param tableInfo 包含更新后结构信息的表结构
     */
    public void updateMeilisearchStructure(TableInfo tableInfo) {
        try {
            // 获取Meilisearch索引
            Index index = meilisearchClient.index(dynamicalConstant.indexName);

            TaskInfo taskInfo = index.updateDocuments(TableInfoIndexBuilder.buildTableInfoIndex(tableInfo));

            log.info("成功更新Meilisearch索引结构: tableSchema={}, tableName={}, taskUid={}",
                    tableInfo.getTableSchema(), tableInfo.getTableName(), taskInfo.getTaskUid());
                    
        } catch (Exception e) {
            log.error("更新Meilisearch索引结构失败: {}", e.getMessage(), e);
            throw new RuntimeException("更新Meilisearch索引结构失败", e);
        }
    }

    /**
     * 将数据库查询结果映射为列信息对象
     * 
     * @param row 数据库查询结果行
     * @return 列信息对象
     */
    private TableInfo.ColumnInfo mapToColumnInfo(Map<String, Object> row) {
        return TableInfo.ColumnInfo.builder()
                .columnName((String) row.get("COLUMN_NAME"))
                .dataType((String) row.get("COLUMN_TYPE"))
                .columnComment((String) row.get("COLUMN_COMMENT"))
                .columnKey((String) row.get("COLUMN_KEY"))
                .isNullable((String) row.get("IS_NULLABLE"))
                .columnDefault((String) row.get("COLUMN_DEFAULT"))
                .build();
    }

    /**
     * 更新数据库表备注
     * 通过ALTER TABLE语句更新表的备注信息
     *
     * @param tableSchema 数据库名
     * @param tableName 表名
     * @param tableComment 新的表备注
     */
    public void updateDatabaseTableComment(String tableSchema, String tableName, String tableComment) {
        String sql = String.format("ALTER TABLE %s.%s COMMENT = '%s'", 
            tableSchema, tableName, tableComment);
        jdbcTemplate.execute(sql);
        log.info("成功更新数据库表备注: tableSchema={}, tableName={}, comment={}", 
            tableSchema, tableName, tableComment);
    }

    /**
     * 更新Meilisearch中的表备注
     * 只更新表备注字段，保持其他字段不变
     *
     * @param tableId 表ID
     * @param tableComment 新的表备注
     */
    public void updateMeilisearchTableComment(Long tableId, String tableComment) {
        try {
            // 获取Meilisearch索引
            Index index = meilisearchClient.index(dynamicalConstant.indexName);
            
            // 构建部分更新文档
            Map<String, Object> document = new HashMap<>();
            document.put("id", tableId);
            document.put("tableComment", tableComment);
            
            // 将文档转换为JSON字符串并更新
            String jsonDocument = new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(document);
            index.updateDocuments(jsonDocument);
            
            log.info("成功更新Meilisearch表备注: tableId={}, comment={}", tableId, tableComment);
                    
        } catch (Exception e) {
            log.error("更新Meilisearch表备注失败: {}", e.getMessage(), e);
            throw new RuntimeException("更新Meilisearch表备注失败", e);
        }
    }

    /**
     * 删除Meilisearch中的表结构
     * @param id 表结构唯一标识
     */
    public void deleteMeilisearchTable(long id) {
        try {
            Index index = meilisearchClient.index(dynamicalConstant.indexName);
            TaskInfo taskInfo = index.deleteDocument(String.valueOf(id));
                log.info("删除Meilisearch表结构任务已提交: id={}，taskUid：{}", id, taskInfo.getTaskUid());
        } catch (Exception e) {
            log.error("删除Meilisearch表结构失败: {}", e.getMessage(), e);
            throw new RuntimeException("删除Meilisearch表结构失败", e);
        }
    }

    /**
     * 初始化：将数据库表结构写入Meilisearch索引
     * @param tableSchema 数据库名
     * @param tableName 表名
     * @return true-成功，false-表不存在
     */
    public boolean initTableToMeilisearch(String tableSchema, String tableName) {
        TableInfo tableInfo = getDatabaseTableStructure(tableSchema, tableName);
        if (tableInfo == null || tableInfo.getTableName() == null || tableInfo.getTableName().isEmpty()) {
            return false;
        }
        try {
            Index index = meilisearchClient.index(dynamicalConstant.indexName);
            // 查询当前最大id
            SearchRequest searchRequest = new SearchRequest("")
                    .setSort(new String[]{"id:desc"})
                    .setLimit(1);
            Searchable searchResult = index.search(searchRequest);
            ArrayList<HashMap<String, Object>> hits = searchResult.getHits();
            long newId = 1L;
            if (!hits.isEmpty() && hits.get(0).get("id") != null) {
                Object maxIdObj = hits.get(0).get("id");
                if (maxIdObj instanceof Number) {
                    newId = ((Number) maxIdObj).longValue() + 1;
                } else if (maxIdObj instanceof String) {
                    newId = Long.parseLong((String) maxIdObj) + 1;
                } else if (maxIdObj instanceof Double) {
                    newId = ((Double) maxIdObj).longValue() + 1;
                }
            }
            tableInfo.setId(newId);
            tableInfo.setTableSchema(tableSchema);
            // 构建文档
            TaskInfo taskInfo = index.addDocuments(TableInfoIndexBuilder.buildTableInfoIndex(tableInfo));
            log.info("写入Meilisearch任务已提交: {}.{}，taskUid：{}", tableSchema, tableName, taskInfo.getTaskUid());
            return true;
        } catch (Exception e) {
            log.error("初始化表结构到Meilisearch失败: {}.{}", tableSchema, tableName, e);
            throw new RuntimeException("初始化表结构到Meilisearch失败", e);
        }
    }

    @PostConstruct
    public void initMeilisearchSortableAttributes() {
        try {
            Index index = meilisearchClient.index(dynamicalConstant.indexName);
            index.updateSortableAttributesSettings(new String[]{"id"});
            log.info("Meilisearch索引[{}]已设置sortableAttributes: id", dynamicalConstant.indexName);
        } catch (Exception e) {
            log.error("设置Meilisearch索引sortableAttributes失败: {}", e.getMessage(), e);
        }
    }
} 